﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using MentalAlchemy.Atomics;
using MentalAlchemy.Molecules;
//using ScriptCompiler;

namespace MentalAlchemy.Molecules
{
	[Serializable]
	public class Variable
	{
		public Type Type;
		public string Name;
		public string Value;
		
		public void Assign (Variable var)
		{
			Type = var.Type;
			Value = var.Value;
			Name = var.Name;
		}
	}

	[Serializable]
	public enum NodeExecutionStatus { Normal, Break, Exit, Error }

	/// <summary>
	/// Class for script execution state.
	/// This class holds:
	/// - list of variables (names and types).
	/// - status after the last node execution.
	/// </summary>
	[Serializable]
	public class ExecutionState
	{
		/// <summary>
		///  Indicates whether local state should be merged with a global state after the node is executed.
		/// </summary>
		public static bool GlobalMerge = true;

		#region - Variables. -
		/// <summary>
		/// Variables.
		/// </summary>
		public Dictionary<string, Variable> Vars = new Dictionary<string, Variable>();

		/// <summary>
		/// Status after the last node execution.
		/// </summary>
		public NodeExecutionStatus CurrentStatus;

		/// <summary>
		/// Stack for BNF constructs.
		/// </summary>
		public Stack<string> BnfStack = new Stack<string>();

		/// <summary>
		/// Stack for variables.
		/// </summary>
		public Stack<Variable> Stack = new Stack<Variable>();

		/// <summary>
		/// List of error messages.
		/// </summary>
		public List<string> Errors { get; set; }

		/// <summary>
		/// Local substate of the current state.
		/// </summary>
		public ExecutionState SubState { get; set; }
		#endregion

		/// <summary>
		/// Merges two states: this and local (arg).
		/// </summary>
		/// <param name="lState"></param>
		/// <returns></returns>
		public void Merge (ExecutionState lState)
		{
			if (lState == null || lState == this) return;
			foreach (var state in lState.Vars)
			{
				if (!Vars.ContainsKey(state.Key))
				{
					Vars.Add(state.Key, state.Value);
				}
			}
			CurrentStatus = lState.CurrentStatus;
		}
	}

	// todo: load environment for interpreter (available external functions, etc.).

	/// <summary>
	/// [molecule]
	/// 
	/// Baseline script executor without local states (all variables are global).
	/// </summary>
	[Serializable]
	public class ScriptExecutor	//: IScriptExecutor
	{
		//public delegate void TreeNodeHandler(ParseTreeNode node, ExecutionState localState);
		public delegate void TreeNodeHandler(ParseTreeNode node);

		public const string DEFAULT_CONDITION_VARIABLE = "condResult";
		public const string DEFAULT_EXPRESSION_VARIABLE = "exprResult";
		public const string DEFAULT_COMMAND_RES_VAR = "AX";

		#region - Public properties. -
		public ExecutionState GlobalState { get; set; }
		public Dictionary<string, TreeNodeHandler> Handlers { get; set; }
		//public List<Variable> Args { get; set; }
		#endregion

		#region - Initialization. -
		public virtual void Init()
		{
			// init node handlers.
			Handlers = new Dictionary<string, TreeNodeHandler>();
			Handlers.Add(BNFExpression.DEFAULT_ASSIGN_BNF_NAME, AssignmentHandler);
			Handlers.Add(BNFExpression.DEFAULT_CONDITION_BNF_NAME, ConditionHandler);
			Handlers.Add(BNFExpression.DEFAULT_CONDITION_EXPR_BNF_NAME, ExpressionHandler);
			Handlers.Add(BNFExpression.DEFAULT_EXPRESSION_BNF_NAME, ExpressionHandler);
			Handlers.Add(BNFExpression.DEFAULT_LOOP_BNF_NAME, LoopHandler);
			Handlers.Add(BNFExpression.DEFAULT_FUNCCALL_BNF_NAME, FunctionCallHandler);
		} 
		#endregion

		#region - Execution methods. -
		public void Execute(ParseTree tree)
		{
			if (tree == null || tree.Nodes.Count == 0)
			{
				throw new Exception("The script tree is either non-initialized or empty.");
			}

			//
			// init execution.
			tree.ResetExecutionStatus();
			GlobalState = new ExecutionState();

			if (tree.Nodes[0].ChildNodes.Count > 0)
			{	// if the script has a body.
				Execute(tree.Nodes[0].ChildNodes);
			}
		}

		public void Execute(ParseTree tree, ExecutionState globalState)
		{
			if (tree == null || tree.Nodes.Count == 0)
			{
				throw new Exception("The script tree is either non-initialized or empty.");
			}

			GlobalState = globalState?? new ExecutionState();
			if (tree.Nodes[0].ChildNodes.Count > 0)
			{	// if the script has a body.
				Execute(tree.Nodes[0].ChildNodes);
			}
		}

		/// <summary>
		/// It's assumed that each sequence of nodes is a local block.
		/// </summary>
		/// <param name="nodes"></param>
		/// <returns></returns>
		public virtual ExecutionState Execute(List<ParseTreeNode> nodes)
		{
			foreach (var node in nodes)
			{
				Execute(node);
				//var res = Execute(node);
				//GlobalState.Merge(res);
			}
			return GlobalState;
			//throw new System.NotImplementedException();
		}

		public virtual void Execute(ParseTreeNode node)
		{
			//
			// todo: process BNF correspondent to the node.
			// todo: and execute current node.
			var bnf = node.Bnf;
			var token = node.Token;

			if (bnf == null)
			{
				// process operator or id.
				if (token.TokenType == TokenType.Operator)
				{
					// exec. operator.
					throw new NotImplementedException("bnf == null");
				}
				else if (token.TokenType == TokenType.Identifier)
				{
					// exec. ID wrt to current operator.
					var value = GetValue(node.Contents, GlobalState);
					var newVar = new Variable();
					newVar.Value = value;
					SetValue(node.Contents, newVar, GlobalState);
				}
				else
				{
					throw new NotImplementedException("Unknown tree node token.");
				}
			}
			else if (bnf.Name != BNFExpression.DEFAULT_HEADER_BNF_NAME)
			{
				if (bnf.Name != BNFExpression.DEFAULT_SEQUENCE_BNF_NAME
					&& bnf.Name != BNFExpression.DEFAULT_SEQUENCE_ITEM_BNF_NAME)
				{
					if (Handlers.ContainsKey(bnf.Name))
					{
						Handlers[bnf.Name](node);
					}
					else
					{
						GlobalState.CurrentStatus = NodeExecutionStatus.Error;
						throw new Exception("Can't find handler for the node: " + bnf.Name);
					}
				}
				else
				{
					GlobalState = Execute(node.ChildNodes);
				}
			}
			else
			{	// todo: process header by taking arguments (if any).
			}

			GlobalState.CurrentStatus = NodeExecutionStatus.Normal;
			node.IsExecuted = true;
			//return GlobalState;
		} 

		/// <summary>
		/// [molecule]
		/// 
		/// Executes specified operator using infromation about the stack.
		/// </summary>
		/// <param name="opName"></param>
		/// <param name="stack"></param>
		/// <param name="glState"></param>
		public static void ExecuteOperator(string opName, Stack<Variable> stack, ExecutionState glState)
		{
			if (string.Compare(opName, BNFExpression.DEFAULT_PLUS_OPERATOR, true) == 0)
			{	// addition.
				// todo: define whether operator is unary or binary.
				// note: temporal solution: if there's only 1 variable in the stack, then the operator is unary.
				// note: more sophisticated (need to be run outside the method): execute the whole expression and if an error is encountered, then some operator should be unary.
				// note: another solution: mark unary operators somehow during conrsion to the Polysh notation.

				// extract 2 variables.
				var rhs = stack.Pop();
				var lhs = stack.Pop();	// inverse order due to stack operations.

				var type = ScriptingElements.GetCommonType(lhs, rhs);
				string res;
				if (type == typeof(float))
				{	// compare numbers.
					var n1 = float.Parse(lhs.Value);
					var n2 = float.Parse(rhs.Value);
					res = (n1 + n2).ToString();
				}
				else
				{	// perform addition of strings.
					res = lhs.Value + rhs.Value;
					//stack.Push(new Variable{Type = typeof(float), Value = res? "1" : "0"});
				}
				stack.Push(new Variable { Type = type, Value = res});
				//throw new NotImplementedException("addition");
			}
			else if (string.Compare(opName, BNFExpression.DEFAULT_MINUS_OPERATOR, true) == 0)
			{	// substraction.
				// todo: define whether operator is unary or binary.
				// note: temporal solution: if there's only 1 variable in the stack, then the operator is unary.
				// note: more sophisticated (need to be run outside the method): execute the whole expression and if an error is encountered, then some operator should be unary.
				// note: another solution: mark unary operators somehow during conrsion to the Polysh notation.

				// extract 2 variables.
				var rhs = stack.Pop();
				var lhs = stack.Pop();	// inverse order due to stack operations.

				var type = ScriptingElements.GetCommonType(lhs, rhs);
				string res;
				if (type == typeof(float))
				{	// compare numbers.
					var n1 = float.Parse(lhs.Value);
					var n2 = float.Parse(rhs.Value);
					res = (n1 - n2).ToString();
					stack.Push(new Variable { Type = type, Value = res });
				}
				else
				{
					var mes = "[operator-] error: Can't handle variables (" + lhs.Value + ", " + rhs.Value + ")";
					glState.Errors.Add(mes);
				}
			}
			else if (string.Compare(opName, BNFExpression.DEFAULT_MULT_OPERATOR, true) == 0)
			{	// multiplication.
				// extract 2 variables.
				var rhs = stack.Pop();
				var lhs = stack.Pop();	// inverse order due to stack operations.

				var type = ScriptingElements.GetCommonType(lhs, rhs);
				string res;
				if (type == typeof(float))
				{	// compare numbers.
					var n1 = float.Parse(lhs.Value);
					var n2 = float.Parse(rhs.Value);
					res = (n1 * n2).ToString();
					stack.Push(new Variable { Type = type, Value = res });
				}
				else
				{	// perform addition of strings.
					var mes = "[operator*] error: Can't handle variables (" + lhs.Value + ", " + rhs.Value + ")";
					glState.Errors.Add(mes);
				}
			}
			else if (string.Compare(opName, BNFExpression.DEFAULT_DIVIDE_OPERATOR, true) == 0)
			{	// multiplication.
				// extract 2 variables.
				var rhs = stack.Pop();
				var lhs = stack.Pop();	// inverse order due to stack operations.

				var type = ScriptingElements.GetCommonType(lhs, rhs);
				string res;
				if (type == typeof(float))
				{	// compare numbers.
					var n1 = float.Parse(lhs.Value);
					var n2 = float.Parse(rhs.Value);
					res = (n1 / n2).ToString();
					stack.Push(new Variable { Type = type, Value = res });
				}
				else
				{	// perform addition of strings.
					var mes = "[operator*] error: Can't handle variables (" + lhs.Value + ", " + rhs.Value + ")";
					glState.Errors.Add(mes);
				}
			}
			else if (string.Compare(opName, BNFExpression.DEFAULT_LESS_OPERATOR, true) == 0)
			{	//
				// extract 2 variables.
				var rhs = stack.Pop();
				var lhs = stack.Pop();	// inverse order due to stack operations.

				// define variables type.
				var type = ScriptingElements.GetCommonType(lhs, rhs);
				bool res;
				if (type == typeof(DateTime))
				{	// compare dates.
					var dt1 = DateTime.Parse(lhs.Value);
					var dt2 = DateTime.Parse(rhs.Value);
					res = dt1.CompareTo(dt2) < 0;
				}
				else if (type == typeof(float))
				{	// compare numbers.
					var n1 = float.Parse(lhs.Value);
					var n2 = float.Parse(rhs.Value);
					res = n1 < n2;
				}
				else
				{	// perform lexicographic comparison.
					res = string.Compare(lhs.Value, rhs.Value) < 0;
					//stack.Push(new Variable{Type = typeof(float), Value = res? "1" : "0"});
				}
				stack.Push(new Variable { Type = type, Value = res ? "1" : "0" });
			}
			else if (string.Compare(opName, BNFExpression.DEFAULT_EQUAL_OPERATOR, true) == 0)
			{	//
				// extract 2 variables.
				var rhs = stack.Pop();
				var lhs = stack.Pop();	// inverse order due to stack operations.

				// define variables type.
				var type = ScriptingElements.GetCommonType(lhs, rhs);
				bool res = lhs.Value == rhs.Value;
				stack.Push(new Variable { Type = type, Value = res ? "1" : "0" });
			}
			else if (string.Compare(opName, BNFExpression.DEFAULT_UNEQUAL_OPERATOR, true) == 0)
			{	//
				// extract 2 variables.
				var rhs = stack.Pop();
				var lhs = stack.Pop();	// inverse order due to stack operations.

				// define variables type.
				var type = ScriptingElements.GetCommonType(lhs, rhs);
				bool res = lhs.Value != rhs.Value;
				stack.Push(new Variable { Type = type, Value = res ? "1" : "0" });
			}
			else if (string.Compare(opName, BNFExpression.DEFAULT_MORE_OPERATOR, true) == 0)
			{
				// extract 2 variables.
				var rhs = stack.Pop();
				var lhs = stack.Pop();	// inverse order due to stack operations.

				// define variables type.
				var type = ScriptingElements.GetCommonType(lhs, rhs);
				bool res;
				if (type == typeof(DateTime))
				{	// compare dates.
					var dt1 = DateTime.Parse(lhs.Value);
					var dt2 = DateTime.Parse(rhs.Value);
					res = dt1.CompareTo(dt2) > 0;
				}
				else if (type == typeof(float))
				{	// compare numbers.
					var n1 = float.Parse(lhs.Value);
					var n2 = float.Parse(rhs.Value);
					res = n1 > n2;
				}
				else
				{	// perform lexicographic comparison.
					res = string.Compare(lhs.Value, rhs.Value) > 0;
				}
				stack.Push(new Variable { Type = type, Value = res ? "1" : "0" });
			}
			else if (string.Compare(opName, BNFExpression.DEFAULT_MORE_EQUAL_OPERATOR, true) == 0)
			{	// implement it as !(lhs < rhs).
				ExecuteOperator(BNFExpression.DEFAULT_LESS_OPERATOR, stack, glState);
				var res = stack.Pop();
				stack.Push(new Variable { Type = res.Type, Value = res.Value == "1" ? "0" : "1" });
			}
			else if (string.Compare(opName, BNFExpression.DEFAULT_LESS_EQUAL_OPERATOR, true) == 0)
			{
				// implement it as !(lhs > rhs).
				ExecuteOperator(BNFExpression.DEFAULT_MORE_OPERATOR, stack, glState);
				var res = stack.Pop();
				stack.Push(new Variable { Type = res.Type, Value = res.Value == "1" ? "0" : "1" }); 
			}
			else if (string.Compare(opName, BNFExpression.DEFAULT_AND_KEYWORD, true) == 0)
			{	// logical AND.
				// extract 2 variables.
				var rhs = stack.Pop();
				var lhs = stack.Pop();	// inverse order due to stack operations.

				// perform operation.
				var arg1 = ParseLogicalArgument(lhs.Value);
				var arg2 = ParseLogicalArgument(rhs.Value);
				var res = arg1 != 0f && arg2 != 0f;
				stack.Push(new Variable { Type = typeof(float), Value = res? "1" : "0" });
			}
			else if (string.Compare(opName, BNFExpression.DEFAULT_OR_KEYWORD, true) == 0)
			{	// logical OR.
				// extract 2 variables.
				var rhs = stack.Pop();
				var lhs = stack.Pop();	// inverse order due to stack operations.

				// perform operation.
				var arg1 = ParseLogicalArgument(lhs.Value);
				var arg2 = ParseLogicalArgument(rhs.Value);
				var res = arg1 != 0f || arg2 != 0f;
				stack.Push(new Variable { Type = typeof(float), Value = res? "1" : "0" });
			}
			else
			{
				throw new NotImplementedException("operator");
			}
		}
		#endregion

		#region - Node handlers. -
		/// <summary>
		/// [molecule]
		/// 
		/// Handler to process assigments node.
		/// </summary>
		/// <param name="node"></param>
		public virtual void AssignmentHandler(ParseTreeNode node)
		{
			// the 1st child is a LHS.
			var lhs = node.ChildNodes[0].Contents;

			// the 2nd child is an assignment operator (can be skipped).
			// the 3rd child is an Expression.
			Handlers[BNFExpression.DEFAULT_EXPRESSION_BNF_NAME](node.ChildNodes[2]);

			// look for the special variable, which contains the expression value.
			var expr = GlobalState.Vars[DEFAULT_EXPRESSION_VARIABLE];
			
			// write the result.
			SetValue(lhs, expr, GlobalState);
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Handler to process conditional node.
		/// </summary>
		/// <param name="node"></param>
		public virtual void ConditionHandler(ParseTreeNode node)
		{
			//var prevGlobalMerge = ExecutionState.GlobalMerge;
			//ExecutionState.GlobalMerge = false;	// enter the local execution state.

			// compute condition.
			Execute(node.ChildNodes[0]);

			// check out [condResult] variable, whether it's [true] or [false].
			var condRes = float.Parse(GlobalState.Vars[DEFAULT_EXPRESSION_VARIABLE].Value);

			// if [true] then launch the first branch.
			// if [false] then launch the second branch.
			if (condRes != 0)
			{
				Execute(node.ChildNodes[1]);
				//var trueBranch = Execute(node.ChildNodes[1]);
				//localState.Merge(trueBranch);
			}
			else
			{
				// todo: process [else if] construct (by recursion).
				// todo: check index!

				// skip ELSE node --> use node.ChildNodes[3].
				if (node.ChildNodes.Count > 3)
				{
					Execute(node.ChildNodes[3]);
					//var falseBranch = Execute(node.ChildNodes[3]);
					//localState.Merge(falseBranch);
				}
				// else there's no else branch!
			}

			//ExecutionState.GlobalMerge = prevGlobalMerge;	// restore the global execution state.
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Handler to process expressions. The node is assumed to contain expression in the Polish notation.
		/// </summary>
		/// <param name="node"></param>
		public virtual void ExpressionHandler(ParseTreeNode node)
		{
			// todo: define the overall expression result type since it can influence the calculation outcomes.

			var stack = new Stack<Variable>();
			foreach (var childNode in node.ChildNodes)
			{
				if (childNode.Token != null && childNode.Token.TokenType == TokenType.Operator)
				{	// execute operator.
					// todo: process unary and binary operators independently.
					// todo: put operator result into the stack.
					ExecuteOperator(childNode.Contents, stack, GlobalState);
				}
				else
				{	// compute variable and put it into the stack.
					string value = "";
					if (childNode.Token != null && Handlers.ContainsKey(childNode.Token.BnfName))
					{
						//if (localState.Vars.ContainsKey(childNode.Contents))
						//{	// extract the local value (it's been already calculated earlier).
						//    value = localState.Vars[childNode.Contents].Value;
						//}
						if (GlobalState.Vars.ContainsKey(childNode.Contents))
						{	// extract the global value (it's been already calculated earlier).
							value = GlobalState.Vars[childNode.Contents].Value;
						}
						else
						{
							Execute(childNode);
							//var res = Execute(childNode);
							// the last entry from [res] is considered as a computation result.
							var iter = GlobalState.Vars.Values.GetEnumerator();
							var idx = GlobalState.Vars.Values.Count;

							// go to the last value.
							for (int i = 0; i < idx; i++) { iter.MoveNext(); }
							value = iter.Current.Value;
						}
					}
					else
					{
						value = GetValue(childNode.Contents, GlobalState);
					}
					var var = new Variable();
					var.Value = value;
					stack.Push(var);
				}
			}

			// the result is assumed to be on the stack's top.
			SetValue(DEFAULT_EXPRESSION_VARIABLE, stack.Pop(), GlobalState);
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Handler to process function calls.
		/// </summary>
		/// <param name="node"></param>
		public virtual void FunctionCallHandler(ParseTreeNode node)
		{
			var funcName = node.ChildNodes[0].Token.Text;

			// compute arguments.
			var args = ConvertToStrings(node.ChildNodes[1].ChildNodes, GlobalState);
			//// note: there's an error, because simple execution of arguments list doesn't provide feasible result as names of variables are unknown.
			//throw new NotImplementedException("Compute arguments!");
			//var argsRes = Execute(node.ChildNodes[1]);

			// call function.
			// it's assumed that function returns its result (if necessary) via special variable in the GlobalState.
			ExecutionEnvironment.Instance.ExecuteFunction(funcName, GlobalState, args);

			// todo: check whether [funcName] is available.

			// todo: check arguments computation results.
			// todo: if ok, then call [funcName] with given arguments.
			// todo: decide whether usual function should be called or that of the subject.
			throw new NotImplementedException();
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Handler for headers.
		/// </summary>
		/// <param name="node"></param>
		public virtual void HeaderHandler(ParseTreeNode node)
		{
			throw new NotImplementedException("HeaderHandler for tree node");

			//
			// obsolete.
			//
			//// if there's nothing to handle.
			//if (node.ChildNodes.Count == 1) {return;}

			//// get names for the arguments.
			//var count = 0;
			//foreach (var argNode in node.ChildNodes[1].ChildNodes)
			//{
			//    var argName = argNode.ChildNodes[0].Contents;
			//    var value = Args != null ? Args[count] : new Variable();
			//    if (GlobalState.Vars.ContainsKey(argName))
			//    {
			//        GlobalState.Vars[argName] = value;
			//    }
			//    else
			//    {
			//        GlobalState.Vars.Add(argName, value);
			//    }
			//}
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Handler to process loops.
		/// </summary>
		/// <param name="node"></param>
		public virtual void LoopHandler(ParseTreeNode node)
		{
			// check condition.
			// compute condition.
			Execute(node.ChildNodes[0]);
			//var cond = Execute(node.ChildNodes[0]);
			// check out [condResult] variable, whether it's [true] or [false].
			var condRes = float.Parse(GlobalState.Vars[DEFAULT_EXPRESSION_VARIABLE].Value);

			while (condRes != 0)
			{	// execute body
				Execute(node.ChildNodes[1]);
				//var trueBranch = Execute(node.ChildNodes[1]);
				//localState.Merge(trueBranch);

				//  update condition.
				//cond = Execute(node.ChildNodes[0]);
				Execute(node.ChildNodes[0]);
				condRes = float.Parse(GlobalState.Vars[DEFAULT_EXPRESSION_VARIABLE].Value);
			}

			//throw new NotImplementedException();
		}
		#endregion

		#region - Static methods. -
		/// <summary>
		/// The methods tries to convert list of (presumably) <ListItem>s represented by a list of nodes into the strings.
		/// All non-<ListItem>s will be skipped.
		/// </summary>
		/// <param name="nodes"></param>
		/// <param name="glState"></param>
		/// <returns></returns>
		public static string[] ConvertToStrings (List<ParseTreeNode> nodes, ExecutionState glState)
		{
			var res = new List<string>();
			foreach (var node in nodes)
			{
				if (node.Bnf.Name == BNFExpression.DEFAULT_LISTITEM_BNF_NAME)
				{
					// todo: make separate method for computing single nodes represented by IDs.
					if (node.ChildNodes[0].Bnf == null)
					{	// the identifier is encountered.
						// check whether it's a variable or a constant.
						// note: Issue: If constant equals to some variable name (like "temp" and temp),
						// note:        then the variable's value will always suppress this constant.
						var item = GetValue(node.ChildNodes[0].Contents, glState);
						res.Add(item);
					}
					else
					{	// compute item value.
						throw new NotImplementedException();
					}
				}
			}
			return res.ToArray();
		}

		public static float ParseLogicalArgument (string arg)
		{
			float res;
			if (!float.TryParse(arg, out res))
			{	// if argument is not a number.
				if (string.Compare(arg, BNFExpression.DEFAULT_TRUE_KEYWORD, true) == 0)
				{	// if the first argument is 
					return 1f;
				}
				if (string.Compare(arg, BNFExpression.DEFAULT_FALSE_KEYWORD, true) == 0)
				{
					return 0f;
				}
				throw new Exception("[ParseLogicalArgument] error: Wrong argument value.");
			}
			return res;
		}

		public static List<string> GetValues (List<string> ids, ExecutionState state)
		{
			var res = new List<string>();
			foreach (var id in ids)
			{
				res.Add(GetValue(id, state));
			}
			return res;
		}

		public static string GetValue (string id, ExecutionState glState)
		{
			if (glState.Vars.ContainsKey(id))
			{
				return glState.Vars[id].Value;
			}
			if (string.Compare(id, BNFExpression.DEFAULT_TRUE_KEYWORD, true) == 0)
			{
				return "1";
			}
			if (string.Compare(id, BNFExpression.DEFAULT_FALSE_KEYWORD, true) == 0)
			{
				return "0";
			}
			return id;
		}

		public static void SetValue(string id, Variable value, ExecutionState glState)
		{
			if (glState.Vars.ContainsKey(id))
			{
				glState.Vars[id].Assign(value);
				return;
			}

			// if no variable is found then create a new one.
			var newVar = new Variable {Type = value.Type, Value = value.Value};
			glState.Vars.Add(id, newVar);
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Returns type of the given variable using its value.
		/// </summary>
		/// <param name="var"></param>
		/// <returns></returns>
		public static Type GetType (Variable var)
		{
			//var.Value;
			throw new NotImplementedException("[GetType]");

			return typeof(Array);
		}

		public static void ExecuteExpression (string[] expr, ExecutionState glState, ExecutionState locState)
		{
			//var stack = new Stack<Variable>();
			foreach (var term in expr)
			{
				if (ScriptingElements.IsMathOperator(term))
				{
					ExecuteOperator(term, glState.Stack, glState);
				}
				else
				{	// compute variable and put it into the stack.
					var value = GetValue(term, glState);

					// todo: handle situation when arguments are not simple variables and constants, but more complicated entities like function calls, arrays indexing etc.
					var var = new Variable();
					var.Value = value;
					glState.Stack.Push(var);
				}
			}

			// the result is written in the execution stack.
		}
		#endregion
	}

	public delegate void CommandHandler(Command cmd, ExecutionState state);

	[Serializable]
	public class ScriptExecutorCmd : ScriptExecutor
	{
		protected List<Command> cmds;
		private int curIdx;

		public Dictionary<string, CommandHandler> CmdHandlers = new Dictionary<string, CommandHandler>();

		public int CurrentCommandIdx
		{
			get { return curIdx; }
			set { curIdx = value; }
		}

		public ScriptExecutorCmd ()
		{
			// init handlers.
			CmdHandlers.Add(Commands.EXIT, ExitHandler);
			CmdHandlers.Add(Commands.EXPRESSION, ExpressionHandler);
			CmdHandlers.Add(Commands.FUNCTION_CALL, FunctionCallHandler);
			CmdHandlers.Add(Commands.JUMP, JumpHandler);
			CmdHandlers.Add(Commands.JUMPIFZERO, JumpIfZeroHandler);
			CmdHandlers.Add(Commands.MOVE, MoveHandler);
			CmdHandlers.Add(Commands.NOP, NopHandler);
			CmdHandlers.Add(Commands.POP, PopHandler);
			CmdHandlers.Add(Commands.PUSH, PushHandler);
		}

		public void Init (List<Command> argCmds)
		{
			cmds = argCmds;
			curIdx = -1;
			GlobalState = new ExecutionState();
		}

		public Command NextCommand ()
		{
			if ( curIdx < 0 ) { curIdx = 0; }
			else { curIdx++; }

			return curIdx < cmds.Count? cmds[curIdx] : null;
		}

		/// <summary>
		/// Executes current command and moves inner iterator.
		/// </summary>
		public void ExecuteStep ()
		{
			// handle negative or too large [curIdx].
			if (curIdx < 0) NextCommand();
			if (curIdx >= cmds.Count) return;

			// todo: handle arguments.

			var cmd = cmds[curIdx];
			Execute(cmd);
//			NextCommand();
		}

		/// <summary>
		/// Executes given command and moves commands iterator.
		/// </summary>
		/// <param name="cmd"></param>
		public void Execute (Command cmd)
		{
			if (cmd == null) return;

			var name = cmd.Name;
			if (CmdHandlers.ContainsKey(name))
			{
				CmdHandlers[name](cmd, GlobalState);
			}
			else
			{
				throw new NotImplementedException("Unknown command: " + cmd.ToString());
			}
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Loads list of compiled commands from the prescribed file.
		/// </summary>
		/// <param name="filename"></param>
		/// <returns></returns>
		public static CompiledScript Load(string filename)
		{
			IFormatter formatter = new BinaryFormatter();
			Stream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None);

			var res = (CompiledScript)formatter.Deserialize(stream);
			stream.Close();

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Saves given set of compiled commands into the specified file.
		/// </summary>
		/// <param name="filename"></param>
		/// <param name="script"></param>
		public static void Save (string filename, CompiledScript script)
		{
			FileIO.Serialize(filename, script);
		}

		#region - Command handlers. -
		public void ExitHandler(Command cmd, ExecutionState estate)
		{
			// jump to the very end of the script.
			curIdx = cmds.Count;
		}

		public void ExpressionHandler(Command cmd, ExecutionState estate)
		{
			var args = ScriptingElements.GetNames(cmd.Args);
			ExecuteExpression(args, estate, estate);

			var res = estate.Stack.Pop();
			SetValue(DEFAULT_COMMAND_RES_VAR, res, estate);
			NextCommand();
		}

		public void FunctionCallHandler (Command cmd, ExecutionState estate)
		{
			var args = ScriptingElements.GetNames(cmd.Args);
			var name = args[0];
			var pars = VectorMath.Subvector(args, 1, args.Length - 1);
			var vals = GetValues(new List<string>(pars), estate);

			ExecutionEnvironment.Instance.ExecuteFunction(name, estate, vals.ToArray());
			var res = estate.Stack.Pop();
			SetValue(DEFAULT_COMMAND_RES_VAR, res, estate);
			NextCommand();
		}

		public void JumpHandler(Command cmd, ExecutionState estate)
		{
			var label = cmd.Args[0].Name;
			var idx = ScriptingElements.FindIndexByLabel(cmds, label);
			if (idx >= 0)
			{
				curIdx = idx;
			}
			else
			{
				throw new NotImplementedException("Can't find label for a jump (" + label + ")");
			}
		}

		public void JumpIfZeroHandler(Command cmd, ExecutionState estate)
		{
			var label = cmd.Args[0].Name;
			// check value of the default register.
			var val = GetValue(DEFAULT_COMMAND_RES_VAR, estate);
			var res = 0f;
			if (float.TryParse(val, out res))
			{
				if (res != 0)
				{	// no jump.
					NextCommand();
				}
				else
				{	// jump.
					var idx = ScriptingElements.FindIndexByLabel(cmds, label);
					if (idx >= 0)
					{
						curIdx = idx;
					}
					else
					{
						throw new NotImplementedException("Can't find label for a conditional jump (" + label + ")");
					}
				}
			}
			else
			{	// not a number, smth else.
				throw new NotImplementedException("Can't compute expression for a conditional jump (" + label + ")");
			}
		}

		public void MoveHandler(Command cmd, ExecutionState estate)
		{
			var lhs = cmd.Args[0].Name;
			var rhs = GetValue(cmd.Args[1].Name, estate);
			cmd.Args[1].Value = rhs;
			SetValue(lhs, cmd.Args[1], estate);
			NextCommand();
		}

		public void NopHandler(Command cmd, ExecutionState estate)
		{	// do nothing.
			NextCommand();
		}

		public void PopHandler(Command cmd, ExecutionState estate)
		{
			var arg = cmd.Args[0].Name;
			if (estate.Stack.Count > 0)
			{	// if stack is not empty.
				var var = estate.Stack.Pop();
				SetValue(arg, var, estate);
			}
			else
			{	// set empty value.
				SetValue(arg, new Variable(), estate);
			}
			NextCommand();
		}

		public void PushHandler(Command cmd, ExecutionState estate)
		{
			var arg = cmd.Args[0].Name;
			var val = GetValue(arg, estate);
			cmd.Args[0].Value = val;
			var res = new Variable { Name = arg, Value = val };
			estate.Stack.Push(res);
			NextCommand();
		}
		#endregion
	}

	/// <summary>
	/// [molecule]
	/// 
	/// Script executor with support for local states.
	/// </summary>
	[Serializable]
	public class ScriptExecutorAdv : ScriptExecutor
	{
		/// <summary>
		/// It's assumed that each sequence of nodes is a local block.
		/// </summary>
		/// <param name="nodes"></param>
		/// <returns></returns>
		public override ExecutionState Execute(List<ParseTreeNode> nodes)
		{
			var state = new ExecutionState();	// local state.
			foreach (var node in nodes)
			{
				Execute(node);
				//var res = Execute(node);
				//if (ExecutionState.GlobalMerge)
				//{
				//    GlobalState.Merge(res);
				//}
				//else
				//{
				//    state.Merge(res);
				//}
			}
			return state;	// it's assumed that each sequence is a local block.
			//throw new System.NotImplementedException();
		}

		public override void Execute(ParseTreeNode node)
		{
			var state = new ExecutionState();	// local state.

			//
			// todo: process BNF correspondent to the node.
			// todo: and execute current node.
			var bnf = node.Bnf;
			var token = node.Token;

			if (bnf == null)
			{
				// process operator or id.
				if (token.TokenType == TokenType.Operator)
				{
					// exec. operator.
					throw new NotImplementedException("bnf == null");
				}
				else if (token.TokenType == TokenType.Identifier)
				{
					// exec. ID wrt to current operator.
					var value = GetValue(node.Contents, GlobalState);
					var newVar = new Variable();
					newVar.Value = value;
					state.Vars.Add(node.Contents, newVar);
				}
				else
				{
					throw new NotImplementedException("Unknown tree node token.");
				}
			}
			else if (bnf.Name != BNFExpression.DEFAULT_HEADER_BNF_NAME)
			{
				if (bnf.Name != BNFExpression.DEFAULT_SEQUENCE_BNF_NAME
					&& bnf.Name != BNFExpression.DEFAULT_SEQUENCE_ITEM_BNF_NAME)
				{
					if (Handlers.ContainsKey(bnf.Name))
					{
						Handlers[bnf.Name](node);
					}
					else
					{
						state.CurrentStatus = NodeExecutionStatus.Error;
						throw new Exception("Can't find handler for the node: " + bnf.Name);
					}
				}
				else
				{
					state = Execute(node.ChildNodes);
				}
			}

			state.CurrentStatus = NodeExecutionStatus.Normal;
			//return state;

			// todo: where to put the code below?

			//
			// Execute child nodes.
			if (node.ChildNodes.Count > 0)
			{
				var childRes = Execute(node.ChildNodes);
				GlobalState.BnfStack.Pop();	// pop-out the last BNF construct name.
				state.Merge(childRes);

				if (childRes.CurrentStatus == NodeExecutionStatus.Normal)
				{
					return;
					//return state;
				}

				// handle abnormal results.
				if (childRes.CurrentStatus == NodeExecutionStatus.Break)
				{	// break current sequence of nodes.
					// todo: probably each node should have 'Breakable' property
					// todo:  to define whether it can be broken and thus return [NodeExecutionStatus.Normal]
					// todo:  or it simply returns [childRes] as it is.

					// todo: !!
					// todo: something is wrong with [Status].
					// todo: !!
					state.CurrentStatus = NodeExecutionStatus.Normal;
					return;
					//return state;
				}
				// return other types of errors without changes.
				return;
				//return childRes;
			}

			//GlobalState.BnfStack.Pop();	// pop-out the last BNF construct name.
			state.CurrentStatus = NodeExecutionStatus.Normal;
			return;
			//return state;
		}
	}

	/// <summary>
	/// [molecule]
	/// 
	/// Class for execution environment, which contains standard (built-in) methods and constants
	/// and also governs available precompiled scripts.
	/// </summary>
	[Serializable]
	public class ExecutionEnvironment
	{
		#region - Constants names. -
		public const string EXP_CONST_NAME = "_exp";
		public const string PI_CONST_NAME = "_pi";
		#endregion

		protected static ExecutionEnvironment env;

		#region - Properties. -
		/// <summary>
		/// Log for environment events.
		/// </summary>
		public List<string> Log { get; set; }

		/// <summary>
		/// List of available precompiled scripts.
		/// </summary>
		public Dictionary<string, CompiledScript> Scripts { get; set; }

		/// <summary>
		/// Handlers for built-in functions (abs, sqrt, sin, cos, exp etc.).
		/// </summary>
		public Dictionary<string, FunctionHandler> FunctionHandlers { get; set; }

		/// <summary>
		/// Set of built-in constants.
		/// </summary>
		public Dictionary<string, Variable> Constants { get; set; } 

		public static ExecutionEnvironment Instance
		{
			get
			{
				if (env == null) {env = new ExecutionEnvironment(null);}
				return env;
			}
		}
		#endregion

		#region - Initialization. -
		/// <summary>
		/// 
		/// </summary>
		/// <param name="scFolder">Folder with precompiled scripts.</param>
		protected ExecutionEnvironment(string scFolder)
		{
			Scripts = new Dictionary<string, CompiledScript>();
			FunctionHandlers = new Dictionary<string, FunctionHandler>();
			Constants = new Dictionary<string, Variable>();

			Init(scFolder);
		}

		public void Init(string scFolder)
		{
			// load all scripts from the specified folder.
			if (Directory.Exists(scFolder))
			{
				Scripts.Clear();

				// load parse trees from files.
				var files = Directory.GetFiles(scFolder);
				foreach (var file in files)
				{
					try
					{
						var script = ScriptExecutorCmd.Load(file);
						Scripts.Add(script.Name, script);
					}
					catch
					{	// file doesn't contain a parse tree.
					}
				}
			}

			// init function handlers.
			InitFunctionHandlers();

			// init constants.
			InitConstants();
		} 

		protected void InitFunctionHandlers ()
		{
			FunctionHandlers = new Dictionary<string, FunctionHandler>();
			FunctionHandlers.Add(ExpHandler.FUNC_NAME, new ExpHandler());
			FunctionHandlers.Add(SinHandler.FUNC_NAME, new SinHandler());
			FunctionHandlers.Add(CosHandler.FUNC_NAME, new CosHandler());
			FunctionHandlers.Add(LogHandler.FUNC_NAME, new LogHandler());
		}

		protected void InitConstants ()
		{
			Constants = new Dictionary<string, Variable>();
			Constants.Add(EXP_CONST_NAME, new Variable { Name = EXP_CONST_NAME, Type = typeof(float), Value = Math.E.ToString()});
			Constants.Add(PI_CONST_NAME, new Variable { Name = PI_CONST_NAME, Type = typeof(float), Value = Math.PI.ToString() });
		}
		#endregion

		/// <summary>
		/// [molecule]
		/// 
		/// Executes requested function.
		/// </summary>
		/// <param name="funcName"></param>
		/// <param name="GlobalState"></param>
		/// <param name="args">*Value* of agruments.</param>
		public void ExecuteFunction(string funcName, ExecutionState GlobalState, string[] args)
		{
			// check if there's a built in function.
			var name = funcName;
			//Variable res;
			if (FunctionHandlers.ContainsKey(name))
			{
				var state = new ExecutionState();
				FunctionHandlers[name].Execute(state, args);

				GlobalState.Stack.Push(state.Stack.Pop());	// get script execution result.
			}
			else if (Scripts.ContainsKey(name))
			{
				var exec = new ScriptExecutorCmd();
				var cmds = Scripts[name].Commands;
				exec.Init(cmds);

				foreach (var arg in args)
				{
					var tempVar = new Variable{Name = arg, Value = arg};
					exec.GlobalState.Stack.Push(tempVar);
				}

				while (exec.CurrentCommandIdx < cmds.Count)
				{
					exec.ExecuteStep();
				}

				// get script execution result.
				GlobalState.Stack.Push(exec.GlobalState.Stack.Pop());	// get script execution result.

				//res = exec.GlobalState.Stack.Count > 0? exec.GlobalState.Stack.Pop() : null;	// get script execution result.
				//if (res != null)
				//{
				//    ScriptExecutor.SetValue(ScriptExecutor.DEFAULT_COMMAND_RES_VAR, res, GlobalState);
				//}
			}
			else
			{
				throw new Exception("ExecuteFunction: " + funcName);
			}

			//if (res != null)
			//{
			//    ScriptExecutor.SetValue(ScriptExecutor.DEFAULT_COMMAND_RES_VAR, res, GlobalState);
			//}
		}
	}

	/// <summary>
	/// Class for function handlers.
	/// </summary>
	public abstract class FunctionHandler
	{
		public string Name;
		public abstract void Execute(ExecutionState estate, string[] args);
	}

	public class ExpHandler : FunctionHandler
	{
		public const string FUNC_NAME = "exp";

		public ExpHandler() { Name = FUNC_NAME; }

		#region Overrides of FunctionHandler
		public override void Execute(ExecutionState estate, string[] args)
		{
			var arg = float.Parse(args[0]);
			var res = new Variable();
			res.Value = Math.Exp(arg).ToString();
			estate.Stack.Push(res);
		}
		#endregion
	}

	public class SinHandler : FunctionHandler
	{
		public const string FUNC_NAME = "sin";

		public SinHandler() { Name = FUNC_NAME; }

		#region Overrides of FunctionHandler
		public override void Execute(ExecutionState estate, string[] args)
		{
			var arg = float.Parse(args[0]);
			var res = new Variable();
			res.Value = Math.Sin(arg).ToString();
			estate.Stack.Push(res);
		}
		#endregion
	}

	public class CosHandler : FunctionHandler
	{
		public const string FUNC_NAME = "cos";

		public CosHandler() { Name = FUNC_NAME; }

		#region Overrides of FunctionHandler
		public override void Execute(ExecutionState estate, string[] args)
		{
			var arg = float.Parse(args[0]);
			var res = new Variable();
			res.Value = Math.Cos(arg).ToString();
			estate.Stack.Push(res);
		}
		#endregion
	}

	public class LogHandler : FunctionHandler
	{
		public const string FUNC_NAME = "log";

		public LogHandler() { Name = FUNC_NAME; }

		#region Overrides of FunctionHandler
		public override void Execute(ExecutionState estate, string[] args)
		{
			var arg = float.Parse(args[0]);
			var res = new Variable();
			res.Value = Math.Log(arg).ToString();
			estate.Stack.Push(res);
		}
		#endregion
	}
}
